package org.thanos.baseline.service;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression;
import org.springframework.stereotype.Service;
import org.thanos.baseline.model.BaselineFactors;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;

@Slf4j
@Service("baselineAlgorithmService")
public class BaselineAlgorithmServiceImpl implements BaselineAlgorithmService {

    /**
     * 矩阵计算方式
     *
     * @param data               计算所得各因子权重
     * @param predictedResultSet 昨天各因子的真实值
     * @return Double 预测的Baseline值
     */
    private BigDecimal predict(double[] data, double[] predictedResultSet) {
        BigDecimal result = BigDecimal.ZERO;
        for (int i = 0; i < data.length; i++) {
            result = result.add(BigDecimal.valueOf(data[i]).multiply(BigDecimal.valueOf(predictedResultSet[i + 1])));
        }
        result = result.add(BigDecimal.valueOf(predictedResultSet[0]));
        return result.setScale(3, RoundingMode.HALF_UP);
    }

    @Override
    @SneakyThrows
    public BigDecimal predict(Integer factorSize, List<BaselineFactors> baselineFactorsList) {
        log.info("-------------------------------Go predict!!--------------------------------");
        // 取得因子个数
        int factorNumber = factorSize;
        // 输入数据的条数，作为二维数组的行数
        int numberOfRows = baselineFactorsList.size();
        log.info("numberOfRows is: {}", numberOfRows);
        log.info("numberOfColumns is: {}", factorNumber);

        // 取得前100条数据，作为行数
        double[] y = new double[numberOfRows - 1];
        double[][] x = new double[numberOfRows - 1][factorNumber];

        // 前一天采信因子的真实值
        double[] x4test = new double[factorNumber];

        for (int i = 0; i < numberOfRows; i++) {
            // 取得每一天的因子及真实电量数据
            BaselineFactors baselineFactor = baselineFactorsList.get(i);
            // 取得前100天的数据，存入相应数组
            if (i != numberOfRows - 1) {
                if (Objects.nonNull(baselineFactor.getYFactor())) {
                    y[i] = baselineFactor.getYFactor().doubleValue();
                }
                for (int j = 1; j <= factorNumber; j++) {
                    Class<?> clazz = Class.forName(BaselineFactors.class.getName());
                    String methodName = "getFactor" + j;
                    Method method = clazz.getMethod(methodName);
                    BigDecimal factorXValue = (BigDecimal) method.invoke(baselineFactor);
                    if (Objects.nonNull(factorXValue)) {
                        x[i][j - 1] = factorXValue.doubleValue();
                    }
                    log.info("x[{}][{}] is: {}", i, j - 1, factorXValue);
                    log.info("y[{}] is: {}", i, baselineFactor.getYFactor());
                }
            } else {
                // 取得昨天的真实数据，作为预测的输入因子。电量作为Y因子，工时/平均温度作为X因子
                for (int j = 1; j <= factorNumber; j++) {
                    Class<?> clazz = Class.forName(BaselineFactors.class.getName());
                    String methodName = "getFactor" + j;
                    Method method = clazz.getMethod(methodName);
                    BigDecimal factorXValue = (BigDecimal) method.invoke(baselineFactor);
                    if (Objects.nonNull(factorXValue)) {
                        x4test[j - 1] = factorXValue.doubleValue();
                    }
                    log.info("x4test[{}] is: {}", j - 1, factorXValue);
                }
            }
        }
        log.info("x is: {}", x);
        log.info("y is: {}", y);
        log.info("x4test is: {}", x4test);

        // 调用apache回归曲线函数，进行训练
        log.info("Go training ...");
        final OLSMultipleLinearRegression regressionFunction = new OLSMultipleLinearRegression();
        // 注入因子
        regressionFunction.newSampleData(y, x);
        // 计算并返回各项权重
        double[] predictedResultSet = regressionFunction.estimateRegressionParameters();

        log.info("predictedResult is: {}", predictedResultSet);
        log.info("Training done!");
        // 根据过往各因子权重，计算baseline值
        return this.predict(x4test, predictedResultSet);
    }
}
